/***********************************************************************************************************************
*                                                                                                                      *
* SPLASH build system v0.1                                                                                             *
*                                                                                                                      *
* Copyright (c) 2013 Andrew D. Zonenberg                                                                               *
* All rights reserved.                                                                                                 *
*                                                                                                                      *
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the     *
* following conditions are met:                                                                                        *
*                                                                                                                      *
*    * Redistributions of source code must retain the above copyright notice, this list of conditions, and the         *
*      following disclaimer.                                                                                           *
*                                                                                                                      *
*    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the       *
*      following disclaimer in the documentation and/or other materials provided with the distribution.                *
*                                                                                                                      *
*    * Neither the name of the author nor the names of any contributors may be used to endorse or promote products     *
*      derived from this software without specific prior written permission.                                           *
*                                                                                                                      *
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED   *
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL *
* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES        *
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR       *
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE       *
* POSSIBILITY OF SUCH DAMAGE.                                                                                          *
*                                                                                                                      *
***********************************************************************************************************************/

#include "splashcore.h"

using namespace std;

NocgenPLL::NocgenPLL(
	const vector<string>& scriptlines,
	unsigned int& i,
	Nocgenv2GeneratedSourceNode* node,
	string fname)
{
	m_diffinput = false;
	
	//Read the script input
	char tmp[128];
	int tmp2;
	char tmp3;
	float tmp4;
	float tmp5;
	for(; i<scriptlines.size(); i++)
	{
		string line = scriptlines[i];
		
		//Look up the opcode
		sscanf(line.c_str(), "%127s", tmp);
		string sopcode(tmp);
		
		//The node name
		if(sopcode == "pll")
		{
			sscanf(line.c_str(), "pll %127s", tmp);
			m_name = tmp;
		}
		
		//The input clock
		else if(sopcode == "inclk")
		{
			if(m_input.m_name != "")
			{
				FatalError("A PLL can only have one clock input (line \"%s\" in pll block of nocgen file \"%s\")\n",
				line.c_str(), fname.c_str());
			}
			
			//Read the input net name
			if(1 != sscanf(line.c_str(), "inclk %127s", tmp))
			{
				FatalError("Malformed inclk line (line \"%s\" in pll block of nocgen file \"%s\")\n",
				line.c_str(), fname.c_str());
			}
					
			//Verify it exists, and is a valid clock net
			if(!node->IsClockNet(tmp))
			{
				//Not an existing clock net? Add it from the BSP
				if(node->IsBSPClockNet(tmp))
					node->AddClockNetFromBSP(tmp);
				else
				{
					FatalError("PLL input \"%s\" is not a BSP clock net (in pll block of nocgen file \"%s\")\n",
						tmp, fname.c_str());
				}
			}
			
			//Grab the clock info
			m_input = node->GetClockNet(tmp);
			
			if(tmp == node->GetSelfClockNet())
			{
				printf("WARNING: PLL input \"%s\" is sourced by the on-chip configuration oscillator which may be unstable\n",
					tmp);
			}
		}
		
		//A differential input clock
		else if(sopcode == "diffclk")
		{
			if(m_input.m_name != "")
			{
				FatalError("A PLL can only have one clock input (line \"%s\" in pll block of nocgen file \"%s\")\n",
				line.c_str(), fname.c_str());
			}
			
			//Read the input net name
			if(1 != sscanf(line.c_str(), "diffclk %127s", tmp))
			{
				FatalError("Malformed diffclk line (line \"%s\" in pll block of nocgen file \"%s\")\n",
				line.c_str(), fname.c_str());
			}
			
			//Format names for the clock nets
			string diffin_p = string(tmp) + "_p";
			string diffin_n = string(tmp) + "_n";
			
			//Verify it exists, and is a valid clock net
			if(!node->IsBSPClockNet(diffin_p))
			{
				FatalError("Differential PLL input \"%s\" is not a BSP clock net (in pll block of nocgen file \"%s\")\n",
					diffin_p.c_str(), tmp, fname.c_str());
			}
			if(!node->IsBSPClockNet(diffin_n))
			{
				FatalError("Differential PLL input \"%s\" is not a BSP clock net (in pll block of nocgen file \"%s\")\n",
					diffin_n.c_str(), tmp, fname.c_str());
			}
			
			//Add the P/N clocks as input pins
			node->AddPinFromBSP(diffin_p, NocgenBSPPin::DIR_IN);
			node->AddPinFromBSP(diffin_n, NocgenBSPPin::DIR_IN);
			
			//Create a new clock based on the two inputs
			NocgenClockNet input_p = node->GetBSPClockNet(diffin_p);
			m_input = NocgenClockNet(
				tmp,
				NocgenClockNet::CLOCK_GLOBAL, 
				input_p.m_freq,
				input_p.m_duty,
				input_p.m_phase,
				"");
			node->AddClockNet(tmp, m_input);
			
			//Remember that we're differential clock
			m_diffinput = true;
		}
		
		//The output clock
		else if( (sopcode == "outclk") || (sopcode == "ioclk") )
		{
			int type = NocgenClockNet::CLOCK_GLOBAL;
			if(sopcode == "ioclk")
				type = NocgenClockNet::CLOCK_FASTIO;
			
			//Read the configuration
			if(5 != sscanf(line.c_str(), "%*s %127s freq %d%c duty %f phase %f",
				tmp, &tmp2, &tmp3, &tmp4, &tmp5))
			{
				FatalError("Malformed outclk line (line \"%s\" in pll block of nocgen file \"%s\")\n",
				line.c_str(), fname.c_str());
			}
			
			//Parse out the frequency
			if(tmp3 == 'M')
				tmp2 *= 1000000;
			else
				FatalError("Bad clock frequency \"%s\" in bsp file \"%s\"\n", line.c_str(), fname.c_str());
			
			NocgenClockNet net(tmp, type, tmp2, tmp4, tmp5, m_input.m_name);
			node->AddClockNet(tmp, net);
			m_outputs.push_back(NocgenPLLNet(net, 1));
		}
		
		//Done
		else if(sopcode == "endpll")
			break;
			
		//Garbage
		else
			FatalError("Unrecognized opcode \"%s\" in pll block of nocgen file \"%s\"\n",
			sopcode.c_str(), fname.c_str());
	}
	
	//Configure the PLL
	ComputeSettings(node);
}

void NocgenPLL::ComputeSettings(Nocgenv2GeneratedSourceNode* node)
{
	//Table of legal PLL configurations
	static const NocgenPLLRestrictions pll_specs[]=
	{
		//spartan-6 PLL (from DS162)
		{ FAMILY_SPARTAN6,	1, 19,  300, 19, 300, 400, 1000, 3125,  500,  52, 64, 128 },
		{ FAMILY_SPARTAN6,	2, 19,  450, 19, 400, 400, 1000, 3125,  950,  52, 64, 128 },
		{ FAMILY_SPARTAN6,	3, 19,  540, 19, 500, 400, 1080, 3125, 1080,  52, 64, 128 },

		//artix-7 MMCM (from DS181)
		{ FAMILY_ARTIX7,	1, 10,  800, 10, 450, 600, 1200, 4690,  800, 106, 64, 128 },
		{ FAMILY_ARTIX7,	2, 10,  800, 10, 500, 600, 1440, 4690,  800, 106, 64, 128 },
		{ FAMILY_ARTIX7,	3, 10,  800, 10, 550, 600, 1600, 4690,  800, 106, 64, 128 },

		//kintex-7 MMCM (from DS182)
		{ FAMILY_KINTEX7,	1, 10,  800, 10, 450, 600, 1200, 4690,  800, 106, 64, 128 },
		{ FAMILY_KINTEX7,	2, 10,  933, 10, 500, 600, 1440, 4690,  933, 106, 64, 128 },
		{ FAMILY_KINTEX7,	3, 10, 1066, 10, 550, 600, 1600, 4690, 1066, 106, 64, 128 }
	};
	
	//Look up our config
	bool found = false;
	unsigned int i;
	for(i=0; i<sizeof(pll_specs)/sizeof(pll_specs[0]); i++)
	{
		if( (pll_specs[i].family == node->GetDeviceFamily()) && (pll_specs[i].speed == node->GetSpeedGrade()) )
		{
			found = true;
			break;
		}
	}
	if(!found)
	{
		FatalError("No nocgen PLL specs could be found for family %d, speed %d\n",
			node->GetDeviceFamily(), node->GetSpeedGrade());
	}
	const NocgenPLLRestrictions& specs = pll_specs[i];
	
	//Convert input frequency to MHz
	float input_mhz = m_input.m_freq / 1000000.0f;
	
	//Compute legal range for input divider so that PFD has a legal input.
	//Also cap by legal divider range
	int indivmin = ceil(input_mhz / specs.pfdmax_mhz);
	int indivmax = floor(input_mhz / specs.pfdmin_mhz);
	if(indivmax > specs.indivmax)
		indivmax = specs.indivmax;
	
	//Compute ideal multiplier range (UG382 equation 3-15, page 102 in rev 1.9)
	//int mulideal = (indivmin * specs.vcomax_mhz) / input_mhz;
	
	//Sanity check that outputs are within the legal range
	for(auto out : m_outputs)
	{
		float target_mhz = out.m_outnet.m_freq / 1000000.0f;
		if(target_mhz > specs.outmax_mhz)
			FatalError("Target PLL output frequency (%.2f MHz) is too high for selected device\n", target_mhz);
		else if(target_mhz < specs.outmin_khz/1000.0f)
			FatalError("Target PLL output frequency (%.2f MHz) is too low for selected device\n", target_mhz);
	}
	
	if(m_outputs.size() > 6)
		FatalError("Requested PLL settings are not possible (too many outputs)\n");
	
	//Loop over legal divider and multiplier values and see if we can make all of the target frequencies
	//TODO: Support fractional multiply/divide for 7 series MMCM
	for(int indiv = indivmin; indiv <= indivmax; indiv ++)
	{
		//Compute frequency at the PFD (guaranteed to be in range by previous checks)
		float pfdfreq = input_mhz / indiv;
		
		//Compute legal multiplier range for this particular divider setting
		//Constrain by both legal VCO range and legal multiplier range
		int mulmin	= ceil(specs.vcomin_mhz / input_mhz) * indiv;
		int mulmax	= floor(indiv*specs.vcomax_mhz / input_mhz);
		if(mulmax > specs.vcomultmax)
			mulmax = specs.vcomultmax;
			
		//For each VCO frequency, we need to check if we can produce the legal output values
		for(int mul = mulmin; mul <= mulmax; mul ++)
		{
			//If multiplier and divider are not relatively prime, we're redundant - skip
			if(gcd(mul, indiv) != 1)
				continue;
				
			//Compute frequency at the VCO
			float vcofreq = pfdfreq * mul;
			
			//Loop over all outputs and check if we can get the target frequency
			//Needs to be within the legal divider range, and an integer fraction of the VCO frequency
			//TODO: Support fractional divide for 7 series MMCM output 0
			bool outputs_ok = true;
			for(auto out : m_outputs)
			{
				float target_mhz = out.m_outnet.m_freq / 1000000.0f;			
				float target_div = vcofreq / target_mhz;
				if( (target_div != floor(target_div)) || (target_div > specs.outdivmax) )
					outputs_ok = false;
			}
			if(!outputs_ok)
				continue;
				
			//TODO: Score options and pick the best
			//For now, pick the first one that we find and stop
			m_indiv = indiv;
			m_mult = mul;
			for(unsigned int i=0; i<m_outputs.size(); i++)
				m_outputs[i].m_clkdiv = vcofreq / (m_outputs[i].m_outnet.m_freq / 1000000.0f);
			return;
		}
	}
	
	FatalError("Requested PLL settings are not possible (no valid divisors)\n");
}

string NocgenPLL::GenerateCode(Nocgenv2GeneratedSourceNode* node)
{
	string code;
	
	//Read the template file
	switch(node->GetDeviceFamily())
	{
		case FAMILY_SPARTAN6:
			code = node->template_get_contents("pll_s6.v");
			break;
			
		case FAMILY_ARTIX7:
			code = node->template_get_contents("mmcm_7series.v");
			break;
			
		case FAMILY_KINTEX7:
			code = node->template_get_contents("mmcm_7series.v");
			break;
			
		default:
			FatalError("NocgenPLL not implemented for this arch");
			break;
	}
	
	//Replace the easy stuff
	code = str_replace("[[instance_name]]", m_name, code);	
	
	//Add the differential input buffer
	string inbuf;
	if(m_diffinput)
	{
		inbuf = node->template_get_contents("ibufds.v");
		inbuf = str_replace("[[name]]", m_input.m_name, inbuf);
	}
	code = str_replace("[[inbuf]]", inbuf, code);
	
	//Input config
	int input_period = 1000000000 / m_input.m_freq;
	char tmp[128];
	snprintf(tmp, sizeof(tmp), "%d", input_period);
	code = str_replace("[[input_period]]", tmp, code);
	float input_mhz = m_input.m_freq / 1000000.0f;
	snprintf(tmp, sizeof(tmp), "%.2f", input_mhz);
	code = str_replace("[[input_freq]]", tmp, code);
	code = str_replace("[[input_net]]", m_input.m_name.c_str(), code);
	
	//PFD config
	float pfd_mhz = input_mhz / m_indiv;
	snprintf(tmp, sizeof(tmp), "%.2f", pfd_mhz);
	code = str_replace("[[pfd_freq]]", tmp, code);
	snprintf(tmp, sizeof(tmp), "%d", m_indiv);
	code = str_replace("[[pfd_div]]", tmp, code);
	
	//VCO config
	float vco_mhz = pfd_mhz * m_mult;
	snprintf(tmp, sizeof(tmp), "%.2f", vco_mhz);
	code = str_replace("[[vco_freq]]", tmp, code);
	snprintf(tmp, sizeof(tmp), "%d", m_mult);
	code = str_replace("[[vco_mult]]", tmp, code);
	
	string clock_nets = "";
	string bufgs = "";
	
	//Output config
	//TODO: Denser packing is possible for DDR clocks on 7 series because the MMCM has negative outputs on 0-3
	//so we can combine phase shifts of 0 and 180 into one output
	for(unsigned int n=0; n<m_outputs.size(); n++)
	{
		string buftmp = "";
		switch(m_outputs[n].m_outnet.m_type)
		{
		case NocgenClockNet::CLOCK_GLOBAL:
			buftmp = node->template_get_contents("bufgce.v");
			break;
		case NocgenClockNet::CLOCK_FASTIO:
			buftmp = node->template_get_contents("bufio.v");
			break;
		default:
			FatalError("invalid clock net type");
		}
		buftmp = str_replace("[[output_net]]", m_outputs[n].m_outnet.m_name.c_str(), buftmp);
		buftmp = str_replace("[[instance_name]]", m_name.c_str(), buftmp);
		buftmp = str_replace("[[input_net]]", (m_outputs[n].m_outnet.m_name + "_raw").c_str(), buftmp);
		
		snprintf(tmp, sizeof(tmp), "[[output_divisor_%d]]", n);
		string sname = tmp;
		snprintf(tmp, sizeof(tmp), "%d", m_outputs[n].m_clkdiv);
		code = str_replace(sname, tmp, code);
		
		snprintf(tmp, sizeof(tmp), "[[output_freq_text_%d]]", n);
		sname = tmp;
		snprintf(tmp, sizeof(tmp), "output %s is %.2f MHz",
			m_outputs[n].m_outnet.m_name.c_str(), vco_mhz / m_outputs[n].m_clkdiv);
		code = str_replace(sname, tmp, code);
		buftmp = str_replace("[[output_freq_text]]", tmp, buftmp);
		
		snprintf(tmp, sizeof(tmp), "[[output_duty_%d]]", n);
		sname = tmp;
		snprintf(tmp, sizeof(tmp), "%.2f", m_outputs[n].m_outnet.m_duty);
		code = str_replace(sname, tmp, code);
		
		snprintf(tmp, sizeof(tmp), "[[output_phase_%d]]", n);
		sname = tmp;
		snprintf(tmp, sizeof(tmp), "%.2f", m_outputs[n].m_outnet.m_phase);
		code = str_replace(sname, tmp, code);
		
		snprintf(tmp, sizeof(tmp), "[[output_net_raw_%d]]", n);
		sname = tmp;
		snprintf(tmp, sizeof(tmp), "%s_raw", m_outputs[n].m_outnet.m_name.c_str());
		code = str_replace(sname, tmp, code);
		
		snprintf(tmp, sizeof(tmp), "	wire %s;		//after BUFGCE\n	wire %s_raw;	//before BUFGCE\n",
			m_outputs[n].m_outnet.m_name.c_str(), m_outputs[n].m_outnet.m_name.c_str());
		clock_nets += tmp;
		
	
		bufgs += buftmp;
	}
	
	//Unused outputs
	for(unsigned int n=m_outputs.size(); n<6; n++)
	{
		snprintf(tmp, sizeof(tmp), "[[output_divisor_%d]]", n);
		code = str_replace(tmp, "1", code);
		
		snprintf(tmp, sizeof(tmp), "[[output_freq_text_%d]]", n);
		code = str_replace(tmp, "unused output", code);
		
		snprintf(tmp, sizeof(tmp), "[[output_duty_%d]]", n);
		code = str_replace(tmp, "0.50", code);
		
		snprintf(tmp, sizeof(tmp), "[[output_phase_%d]]", n);
		code = str_replace(tmp, "0.0", code);
		
		snprintf(tmp, sizeof(tmp), "[[output_net_raw_%d]]", n);
		code = str_replace(tmp, "", code);
	}
	
	//Replace the clock net list
	code = str_replace("[[clock_nets]]", clock_nets, code);
	code = str_replace("[[bufgs]]", bufgs, code);
	
	return code;
}
